/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.iec.edp.caf.tenancy.core.service;

import io.iec.caf.data.jpa.utils.TemporaryHibernateIdentifier;
import io.iec.edp.caf.commons.exception.ExceptionLevel;
import io.iec.edp.caf.commons.transaction.JpaTransaction;
import io.iec.edp.caf.commons.transaction.TransactionPropagation;
import io.iec.edp.caf.commons.utils.SpringBeanUtils;
import io.iec.edp.caf.data.multilang.CAFMultiLanguageColumn;
import io.iec.edp.caf.i18n.api.LanguageSuffixProvider;
import io.iec.edp.caf.tenancy.api.entity.AppInstanceInfo;
import io.iec.edp.caf.tenancy.api.entity.EmbeddedTenant;
import io.iec.edp.caf.tenancy.api.entity.Tenant;
import io.iec.edp.caf.tenancy.api.exception.TenantException;
import io.iec.edp.caf.tenancy.api.service.TenantAppInstanceService;
import io.iec.edp.caf.tenancy.api.service.TenantPersistenceService;
import io.iec.edp.caf.tenancy.core.TenantCache;
import io.iec.edp.caf.tenancy.core.repository.TenantRepository;
import io.iec.edp.caf.tenancy.core.utils.DataValidator;
import lombok.var;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import io.iec.edp.caf.commons.thirdext.persister.entity.PersistersContext;

import java.util.*;

/**
 * TenantManager$
 *
 * @author Hey Law
 * @create 2019年8月22日09:54:37$
 */
public class TenantManager implements TenantPersistenceService {

    private final TenantRepository repository;

    private final Log log = LogFactory.getLog(TenantManager.class);

//    private EcpLanguageRepository langRepository;

    //所有租户的缓存
    private final Map<String,List<Tenant>> allTenantsCache = new HashMap<>();

    //具体某一个租户的缓存（分布式缓存）
    private final TenantCache tenantCache;

    private final TenantAppInstanceService appInstMgr;
    private LanguageSuffixProvider suffixProvider;
    public TenantManager(TenantRepository repository, TenantCache tenantCache,TenantAppInstanceService appInstMgr,LanguageSuffixProvider suffixProvider)
    {
        this.repository = repository;
        this.tenantCache = tenantCache;
        this.appInstMgr=appInstMgr;
        this.suffixProvider=suffixProvider;
    }

    /// <summary>
    /// 根据租户标识获取详细信息
    /// </summary>
    /// <param name="tenantId">租户标识</param>
    /// <returns>租户信息</returns>
    /**
     * 根据租户标识获取详细信息
     *
     * @param tenantId 租户标识
     * @param language 语种编号
     * @return
     * @Date: 2019/8/22
     */
    public Tenant get(int tenantId, String language)
    {
        DataValidator.checkForNullReference(tenantId, "tenantId");

        String cacheKey = this.getCacheKey(language, tenantId);
        var tenant = tenantCache.get(cacheKey, Tenant.class);
        try
        {
            if (tenant == null) {
                setLanguageFieldSuffix(language);
                var tmp = this.repository.findById(tenantId);
                if (tmp.isPresent()) {
                    tenant = new Tenant(tmp.get(),language);
                }
                this.addCache(tenantId, language, tenant);
            }
        }
        catch (Exception ex)
        {
            tenant = null;
        }

        return tenant;
    }

    public void saveTenant(Tenant tenant){
        //清一把缓存(没法更新缓存，因为allTenantsCache存的value是个List)
        allTenantsCache.clear();
        saveEmbeddedTenant(tenant);
    }

    public void deleteTenantById(Integer tenantId){
        Optional<EmbeddedTenant> result=this.repository.findById(tenantId);
        if(result.isPresent()){
            this.repository.deleteById(tenantId);
        }
    }
    /**
     * 根据租户标识获取详细信息
     *
     * @param language 语种编号
     * @return
     * @Date: 2019/8/22
     */
    public List<Tenant> getAllTenants(String language)
    {
        //若已缓存
        if(allTenantsCache.containsKey(language)) {
//            return allTenantsCache.get(language)
            //BEF组为支持多租户级的缓存同时兼容之前未考虑多租户缓存的场景，在调用getAllTenants获取租户后，会在结果中塞一个不存在的
            //租户，这样会影响其他人调用该方法时也获取到BEF组塞的不存在的租户，故将返回值改成每次新创建一个List
            return new ArrayList<>(allTenantsCache.get(language));
        }
        else{
            setLanguageFieldSuffix(language);
            List<EmbeddedTenant> allEmbeddedTenants = this.repository.findAll();
            List<Tenant> allTenants=new ArrayList<>();
            if(allEmbeddedTenants!=null&&allEmbeddedTenants.size()>0){
                for(EmbeddedTenant embeddedTenant:allEmbeddedTenants){
                    allTenants.add(new Tenant(embeddedTenant,language));
                }
            }
            allTenantsCache.put(language,allTenants);
            return allTenants;
        }
    }

    /// <summary>
    /// 根据租户编号获取详细信息
    /// </summary>
    /// <param name="tenantCode">租户编号</param>
    /// <returns>租户信息</returns>
    public Tenant getByCode(String tenantCode, String language) {
        DataValidator.checkForEmptyString(tenantCode, "tenantCode");

        setLanguageFieldSuffix(language);
        EmbeddedTenant embeddedTenant=this.repository.findByCode(tenantCode);
        Tenant tenant=null;
        if(embeddedTenant!=null){
            tenant=new Tenant(embeddedTenant,language);
        }
        return tenant;
    }

    /**
     * 更新租户信息
     *
     * @param tenant 租户
     * @param language 语种编号
     * @return
     * @Date: 2019/8/22
     */
    public void add(Tenant tenant,String language)
    {
        DataValidator.checkForNullReference(tenant, "Tenant");

        if (tenant != null)
        {
            setLanguageFieldSuffix(language);
            saveEmbeddedTenant(tenant);
            this.addCache(tenant.getId(), language, tenant);
        }
    }

    /**
     * 删除租户信息
     *
     * @param id 租户Id
     * @return
     * @Date: 2019/8/22
     */
    public boolean remove(int id)
    {
        DataValidator.checkForNullReference(id, "TenantId");

        this.repository.deleteById(id);
        this.removeCache(id);
        return true;
    }

    public void updateTenant(Tenant tenant,int oldTenantId){
        JpaTransaction tran = JpaTransaction.getTransaction();
        try {
            tran.begin(TransactionPropagation.REQUIRES_NEW);
            this.saveTenant(tenant);
            if(oldTenantId!=tenant.getId()) {
                this.deleteTenantById(oldTenantId);
            }
            List<AppInstanceInfo> apps = appInstMgr.getAllAppInstInfos(oldTenantId);
            apps.forEach(app->{
                app.setTenantId(tenant.getId());
                appInstMgr.update(app);
            });
            tran.commit();
        } catch (Throwable e) {
            try{
                tran.rollback();
            }catch (Throwable throwable){
                log.error("updateTenantRollbackError: ",throwable);
            }
            throw new TenantException("caf","update-tenant" ,"update-tenant",e, ExceptionLevel.Error,false);
        }
    }

    private void saveEmbeddedTenant(Tenant tenant) {
        Optional<EmbeddedTenant> result = this.repository.findById(tenant.getId());
        if (result.isPresent()) {
            EmbeddedTenant embeddedTenant = new EmbeddedTenant(tenant);
            EmbeddedTenant selectTenant = result.get();
            selectTenant.getName().setValue(tenant.getName());
            embeddedTenant.setName(selectTenant.getName());
            this.repository.save(embeddedTenant);
        }else{
            EmbeddedTenant embeddedTenant=new EmbeddedTenant(tenant);
            embeddedTenant.setName(new CAFMultiLanguageColumn());
            embeddedTenant.getName().setValue(tenant.getName());
            this.repository.save(embeddedTenant);
        }
    }
    /**
     * 根据租户标识获取缓存key
     *
     * @param language 语种编号
     * @param tenantId 租户标识
     * @return
     * @Date: 2019/8/22
     */
    private String getCacheKey(String language,int tenantId)
    {
        return String.format("Tenant_%s_%s", language, tenantId);
    }


    /**
     * 加入缓存
     *
     * @param language 语种编号
     * @param tenantId 租户标识\
     * @param tenant  租户
     * @return
     * @Date: 2019/8/22
     */
    private void addCache(int tenantId, String language, Tenant tenant)
    {
        String cacheKey = this.getCacheKey(language, tenantId);
        tenantCache.set(cacheKey, tenant);

        //更新语言的索引,此处有并发问题，后续完善
        String cacheKey2 = String.format("Tenant_%s_index", tenantId);

        List<String> arr = new ArrayList<>();
        arr = tenantCache.get(cacheKey2,arr.getClass());
        if (arr == null) {
            arr = new ArrayList<>();
            tenantCache.set(cacheKey2, arr);
        }
    }

    /**
     * 删除缓存
     *
     * @param
     * @return
     * @Date: 2019/8/22
     */
    private void removeCache(int tenantId)
    {
        //更新语言的索引,此处有并发问题，后续完善
        String cacheKey = String.format("Tenant_%s_index", tenantId);

        List<String> arr = new ArrayList<>();
        arr = tenantCache.get(cacheKey,arr.getClass());
        if (arr == null)
        {
            arr = new ArrayList<>();
        }

        for (var item : arr)
        {
            tenantCache.remove(item);
        }
        tenantCache.remove(cacheKey);
    }

    //设置语言后缀 TODO hibernate最终升级后删除
    @Deprecated
    private void setLanguageFieldSuffix(String language) {
        if (!TemporaryHibernateIdentifier.NATIVE) {
            //bef在启动阶段初始化其bean时调用了ITenantService的getAllTenants方法，导致启动阶段逻辑进入该方法后通过SpringBeanUtils获取的LanguageSuffixProvider可能为null,故改成构造函数里注入
//            LanguageSuffixProvider suffixProvider = SpringBeanUtils.getBean(LanguageSuffixProvider.class);
            String currentLangSuffix =this.suffixProvider.getFieldSuffix(language);

            String ctxLangSuffix = PersistersContext.getCurrentLangSuffix();
            if (!currentLangSuffix.equals(ctxLangSuffix))
                PersistersContext.setCurrentLangSuffix(currentLangSuffix);
        }
    }

}
